﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Reflection;
using NLite.Log;
using NLite.Collections;
using NLite.Interceptor.Fluent;
using NLite.Internal;
using NLite.Interceptor.Metadata;

namespace NLite.Interceptor
{
    /// <summary>
    /// 
    /// </summary>
    [Flags]
    public enum AdviceType
    {
        /// <summary>
        /// 
        /// </summary>
        None = 1,
        /// <summary>
        /// 
        /// </summary>
        Before = 2,
        /// <summary>
        /// 
        /// </summary>
        After = 4,
        /// <summary>
        /// 
        /// </summary>
        Exception = 8,
        /// <summary>
        /// 
        /// </summary>
        Around = Before | After,
        /// <summary>
        /// 
        /// </summary>
        All = Around | Exception
    }

    /// <summary>
    /// 
    /// </summary>
    public interface IInterceptor
    {
        /// <summary>
        /// 
        /// </summary>
        /// <param name="ctx"></param>
        void OnInvocationExecuting(IInvocationExecutingContext ctx);
        /// <summary>
        /// 
        /// </summary>
        /// <param name="ctx"></param>
        void OnInvocationExecuted(IInovacationExecutedContext ctx);
        /// <summary>
        /// 
        /// </summary>
        /// <param name="ctx"></param>
        void OnException(IInvocationExceptionContext ctx);
    }

    /// <summary>
    /// 
    /// </summary>
    public interface IInvocationContext
    {
        /// <summary>
        /// 
        /// </summary>
        object Target { get; }
        /// <summary>
        /// 
        /// </summary>
        MethodInfo Method { get; }
        /// <summary>
        /// 
        /// </summary>
        object[] Arguments { get; }
    }

    /// <summary>
    /// 
    /// </summary>
    public interface IInvocationExecutingContext : IInvocationContext
    {
        
    }

    /// <summary>
    /// 
    /// </summary>
    public interface IInovacationExecutedContext : IInvocationContext
    {
        /// <summary>
        /// 
        /// </summary>
        object Result { get; set; }
    }

    /// <summary>
    /// 
    /// </summary>
    public interface IInvocationExceptionContext : IInovacationExecutedContext
    {
        /// <summary>
        /// 
        /// </summary>
        Exception Exception { get; set; }
        /// <summary>
        /// 
        /// </summary>
        bool ExceptionHandled { get; set; }
    }

    /// <summary>
    /// 
    /// </summary>
    public class DefaultInterceptor : IInterceptor
    {
        /// <summary>
        /// 
        /// </summary>
        /// <param name="ctx"></param>
        public virtual void OnInvocationExecuting(IInvocationExecutingContext  ctx)
        {
        }

        /// <summary>
        /// 
        /// </summary>
        /// <param name="ctx"></param>
        public virtual void OnInvocationExecuted(IInovacationExecutedContext ctx)
        {
        }

        /// <summary>
        /// 
        /// </summary>
        /// <param name="ctx"></param>
        public virtual void OnException(IInvocationExceptionContext ctx)
        {
        }
    }

   


    /// <summary>
    /// 
    /// </summary>
    public sealed class InterceptorBroker:IInterceptor
    {
        private IInterceptorRepository Repository;

        /// <summary>
        /// 
        /// </summary>
        public InterceptorBroker()
        {
            Repository = ServiceLocator.Get<IInterceptorRepository>();
        }

        /// <summary>
        /// 
        /// </summary>
        /// <param name="ctx"></param>
        public void OnInvocationExecuting(IInvocationExecutingContext ctx)
        {
            var interceptors = GetInterceptors(ctx.Method as MethodInfo);
            if (interceptors.Length == 0)
                return;

            foreach (var interceptor in interceptors)
                interceptor.OnInvocationExecuting(ctx);
        }

        /// <summary>
        /// 
        /// </summary>
        /// <param name="ctx"></param>
        public void OnInvocationExecuted(IInovacationExecutedContext ctx)
        {
            var interceptors = GetInterceptors(ctx.Method as MethodInfo);
            if (interceptors.Length == 0)
                return;
            foreach (var interceptor in interceptors)
                interceptor.OnInvocationExecuted(ctx);

        }

        /// <summary>
        /// 
        /// </summary>
        /// <param name="ctx"></param>
        public void OnException(IInvocationExceptionContext ctx)
        {
            var interceptors = GetInterceptors(ctx.Method as MethodInfo);
            if (interceptors.Length == 0)
                return;
            foreach (var interceptor in interceptors)
            {
                interceptor.OnException(ctx);
                if (ctx.ExceptionHandled)
                    break;
            }
        }

        private IInterceptor[] GetInterceptors(MethodInfo methodInfo)
        {
            return Repository.Get(methodInfo).ToArray();
        }
    }

    /// <summary>
    /// 
    /// </summary>
    public static class Aspect
    {
        /// <summary>
        /// 
        /// </summary>
        /// <param name="namespace"></param>
        /// <returns></returns>
        public static INamespaceExpression FromNamespace(string @namespace)
        {
            var aspect = new NamespaceExpression(@namespace);
            ServiceLocator.Get<IAspectRepository>().Register(aspect.ToAspect());
            return aspect;
        }
        /// <summary>
        /// 
        /// </summary>
        /// <typeparam name="T"></typeparam>
        /// <returns></returns>
        public static IAspectExpression For<T>()
        {
            return For(typeof(T));
        }
        /// <summary>
        /// 
        /// </summary>
        /// <param name="componentType"></param>
        /// <returns></returns>
        public static IAspectExpression For(Type componentType)
        {
            var aspect = new SingleTypeExpression(componentType);
            ServiceLocator.Get<IAspectRepository>().Register(aspect.ToAspect());
            return aspect;
        }
    }


    /// <summary>
    /// 
    /// </summary>
    [Contract]
    public interface IInterceptorRepository
    {
        /// <summary>
        /// 
        /// </summary>
        /// <param name="method"></param>
        /// <returns></returns>
        ICollection<IInterceptor> Get(MethodInfo method);
    }

    
    /// <summary>
    /// 
    /// </summary>
    public class InterceptorRepository : IInterceptorRepository
    {
        private IMap<MethodBase, ICollection<IInterceptor>> Cache = new ConcurrentMap<MethodBase, ICollection<IInterceptor>>();

        /// <summary>
        /// 
        /// </summary>
        /// <param name="method"></param>
        /// <returns></returns>
        public ICollection<IInterceptor> Get(MethodInfo method)
        {
            return Cache.GetOrAdd(method.GetBaseDefinition(), () => new List<IInterceptor>());
        }
    }
}
